/*
 * Frame.java
 * Created on 14-Sep-2009, 16:37:24
 *
 * Provides a 3D GUI to display Ubisense RTLS data
 *

   Copyright 2009 BBC Research, David Lewis, <david.lewis4@bbc.co.uk>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


 */
package ubi3d;

import com.sun.j3d.utils.behaviors.mouse.MouseBehavior;
import com.sun.j3d.utils.behaviors.mouse.MouseRotate;
import com.sun.j3d.utils.behaviors.mouse.MouseTranslate;
import com.sun.j3d.utils.behaviors.mouse.MouseZoom;
import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.image.TextureLoader;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.Viewer;
import com.sun.j3d.utils.universe.ViewingPlatform;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.Window;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Bounds;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.Font3D;
import javax.media.j3d.FontExtrusion;
import javax.media.j3d.GraphicsConfigTemplate3D;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.Material;
import javax.media.j3d.RenderingAttributes;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Text3D;
import javax.media.j3d.Texture;
import javax.media.j3d.Texture2D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TransparencyAttributes;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPopupMenu;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Quat4d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;
import ubi3d.Data.DataSource;

/**
 *
 * @author davidle
 */
public class Frame extends javax.swing.JFrame {

    /** Creates new form Frame */
    public Frame() {
        initComponents();
        this.setExtendedState(JFrame.MAXIMIZED_BOTH);
        this.setVisible(true);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jTextField1 = new javax.swing.JTextField();
        jButtonWS = new javax.swing.JButton();
        jPanel3Dview = new javax.swing.JPanel();
        jSeparator2 = new javax.swing.JSeparator();
        jTextField2 = new javax.swing.JTextField();
        jProgressBar1 = new javax.swing.JProgressBar();
        jFormattedTextField1 = new javax.swing.JFormattedTextField();
        jPanel4 = new javax.swing.JPanel();
        jPanel3 = new javax.swing.JPanel();
        jPanel2 = new javax.swing.JPanel();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        jLabel3 = new javax.swing.JLabel();
        jLabel4 = new javax.swing.JLabel();
        jLabel5 = new javax.swing.JLabel();
        jLabel6 = new javax.swing.JLabel();
        tilt = new javax.swing.JTextField();
        pan = new javax.swing.JTextField();
        posZ = new javax.swing.JTextField();
        posY = new javax.swing.JTextField();
        posX = new javax.swing.JTextField();
        jButtonDB = new javax.swing.JButton();
        jLabelH = new javax.swing.JLabel();
        jLabelM = new javax.swing.JLabel();
        jLabelS = new javax.swing.JLabel();
        jLabelMs = new javax.swing.JLabel();
        jComboBoxTable = new javax.swing.JComboBox();
        jButtonForward = new javax.swing.JButton();
        jButtonBack = new javax.swing.JButton();
        jButtonLive = new javax.swing.JButton();
        jLabelFrame = new javax.swing.JLabel();
        jButtonStop = new javax.swing.JButton();
        jLabelvisible = new javax.swing.JLabel();
        jMenuBar1 = new javax.swing.JMenuBar();
        jMenu1 = new javax.swing.JMenu();
        jMenuCam = new javax.swing.JMenu();
        jMenuCamOffice = new javax.swing.JMenuItem();
        jMenuCamRoof = new javax.swing.JMenuItem();
        jMenuCamPlan = new javax.swing.JMenuItem();
        jMenuCamGround = new javax.swing.JMenuItem();
        jSeparator1 = new javax.swing.JSeparator();
        jCheckBoxCamTrack = new javax.swing.JCheckBoxMenuItem();
        jMenuOptions = new javax.swing.JMenu();
        jCheckBoxFilter = new javax.swing.JCheckBoxMenuItem();
        jCheckBoxHeight = new javax.swing.JCheckBoxMenuItem();
        jCheckBoxXinvert = new javax.swing.JCheckBoxMenuItem();
        jCheckBoxIcons = new javax.swing.JCheckBoxMenuItem();
        jSeparator3 = new javax.swing.JSeparator();
        jMenuClear = new javax.swing.JMenuItem();
        jMenuMax = new javax.swing.JMenuItem();

        jTextField1.setText("jTextField1");

        jButtonWS.setText("WS");

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Ubisense 3D output");
        setBackground(new java.awt.Color(102, 102, 102));
        addComponentListener(new java.awt.event.ComponentAdapter() {
            public void componentShown(java.awt.event.ComponentEvent evt) {
                formComponentShown(evt);
            }
        });

        jPanel3Dview.setBackground(new java.awt.Color(0, 0, 0));
        jPanel3Dview.setMaximumSize(new java.awt.Dimension(1024, 576));
        jPanel3Dview.setPreferredSize(new java.awt.Dimension(1024, 576));
        jPanel3Dview.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyTyped(java.awt.event.KeyEvent evt) {
                jPanel3DviewKeyTyped(evt);
            }
        });

        javax.swing.GroupLayout jPanel3DviewLayout = new javax.swing.GroupLayout(jPanel3Dview);
        jPanel3Dview.setLayout(jPanel3DviewLayout);
        jPanel3DviewLayout.setHorizontalGroup(
            jPanel3DviewLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 1284, Short.MAX_VALUE)
        );
        jPanel3DviewLayout.setVerticalGroup(
            jPanel3DviewLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 766, Short.MAX_VALUE)
        );

        jTextField2.setText("jTextField2");

        jFormattedTextField1.setText("jFormattedTextField1");

        jPanel3.setBackground(new java.awt.Color(255, 255, 255));

        javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
        jPanel3.setLayout(jPanel3Layout);
        jPanel3Layout.setHorizontalGroup(
            jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 338, Short.MAX_VALUE)
        );
        jPanel3Layout.setVerticalGroup(
            jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 319, Short.MAX_VALUE)
        );

        jPanel2.addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
            public void mouseMoved(java.awt.event.MouseEvent evt) {
                jPanel2MouseMoved(evt);
            }
        });

        jLabel1.setText("Camera Position:");

        jLabel2.setText("x:");

        jLabel3.setText("y:");

        jLabel4.setText("z:");

        jLabel5.setText("Pan:");

        jLabel6.setText("Tilt:");

        jButtonDB.setText("DB");
        jButtonDB.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonDBActionPerformed(evt);
            }
        });

        jLabelH.setText("h");

        jLabelM.setText("m");

        jLabelS.setText("s");

        jLabelMs.setText("ms");

        jComboBoxTable.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "recovered2", "recoveredA", "db2", "football", "recovered", "occ1", "occ2", "occ3", "occ4", "direction", "260110bmod", "Cricket", "Cricket2", "Laps", "Sprint" }));

        jButtonForward.setText(">>");
        jButtonForward.setMargin(new java.awt.Insets(2, 4, 2, 4));
        jButtonForward.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonForwardActionPerformed(evt);
            }
        });

        jButtonBack.setText("<<");
        jButtonBack.setMargin(new java.awt.Insets(2, 4, 2, 4));
        jButtonBack.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonBackActionPerformed(evt);
            }
        });

        jButtonLive.setText("Live");
        jButtonLive.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonLiveActionPerformed(evt);
            }
        });

        jLabelFrame.setText(" ");

        jButtonStop.setText("[]");
        jButtonStop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonStopActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
        jPanel2.setLayout(jPanel2Layout);
        jPanel2Layout.setHorizontalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(jPanel2Layout.createSequentialGroup()
                                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                    .addComponent(jLabel6)
                                    .addComponent(jLabel5))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                    .addComponent(tilt, javax.swing.GroupLayout.DEFAULT_SIZE, 79, Short.MAX_VALUE)
                                    .addComponent(pan, javax.swing.GroupLayout.DEFAULT_SIZE, 79, Short.MAX_VALUE)))
                            .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                                .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel2Layout.createSequentialGroup()
                                    .addComponent(jLabel4)
                                    .addGap(18, 18, 18)
                                    .addComponent(posZ))
                                .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel2Layout.createSequentialGroup()
                                    .addComponent(jLabel3)
                                    .addGap(18, 18, 18)
                                    .addComponent(posY))
                                .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel2Layout.createSequentialGroup()
                                    .addComponent(jLabel2)
                                    .addGap(18, 18, 18)
                                    .addComponent(posX))
                                .addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING))
                            .addComponent(jComboBoxTable, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addGap(336, 336, 336))
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addComponent(jButtonDB)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jButtonStop))
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addComponent(jLabelH)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jLabelM)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jLabelS)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jLabelMs)
                        .addGap(315, 315, 315))
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addComponent(jButtonBack)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jButtonForward)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jLabelFrame, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addGap(370, 370, 370))
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addComponent(jButtonLive)
                        .addContainerGap(267, Short.MAX_VALUE))))
        );
        jPanel2Layout.setVerticalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel2)
                    .addComponent(posX, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel3)
                    .addComponent(posY, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel4)
                    .addComponent(posZ, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel5)
                    .addComponent(pan, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel6)
                    .addComponent(tilt, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jComboBoxTable, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jButtonDB)
                    .addComponent(jButtonStop))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabelH)
                    .addComponent(jLabelM)
                    .addComponent(jLabelS)
                    .addComponent(jLabelMs))
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jLabelFrame))
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addGap(16, 16, 16)
                        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(jButtonForward)
                            .addComponent(jButtonBack))))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jButtonLive)
                .addGap(50, 50, 50))
        );

        jLabelvisible.setText("Tag visible");

        javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4);
        jPanel4.setLayout(jPanel4Layout);
        jPanel4Layout.setHorizontalGroup(
            jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel4Layout.createSequentialGroup()
                .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(jPanel4Layout.createSequentialGroup()
                        .addContainerGap()
                        .addComponent(jLabelvisible))
                    .addGroup(jPanel4Layout.createSequentialGroup()
                        .addContainerGap()
                        .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, 328, Short.MAX_VALUE))
                    .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap())
        );
        jPanel4Layout.setVerticalGroup(
            jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel4Layout.createSequentialGroup()
                .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabelvisible)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 46, Short.MAX_VALUE)
                .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, 381, javax.swing.GroupLayout.PREFERRED_SIZE))
        );

        jMenu1.setText("File");
        jMenuBar1.add(jMenu1);

        jMenuCam.setText("Camera");
        jMenuCam.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                CamChange(evt);
            }
        });

        jMenuCamOffice.setText("Office");
        jMenuCamOffice.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                CamChange(evt);
            }
        });
        jMenuCam.add(jMenuCamOffice);

        jMenuCamRoof.setText("Roof");
        jMenuCamRoof.setEnabled(false);
        jMenuCamRoof.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                CamChange(evt);
            }
        });
        jMenuCam.add(jMenuCamRoof);

        jMenuCamPlan.setText("Bird's eye view");
        jMenuCamPlan.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                CamChange(evt);
            }
        });
        jMenuCam.add(jMenuCamPlan);

        jMenuCamGround.setText("Ground");
        jMenuCamGround.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                CamChange(evt);
            }
        });
        jMenuCam.add(jMenuCamGround);
        jMenuCam.add(jSeparator1);

        jCheckBoxCamTrack.setText("Tracking");
        jMenuCam.add(jCheckBoxCamTrack);

        jMenuBar1.add(jMenuCam);

        jMenuOptions.setText("Options");

        jCheckBoxFilter.setText("Filter");
        jCheckBoxFilter.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBoxFilterActionPerformed(evt);
            }
        });
        jMenuOptions.add(jCheckBoxFilter);

        jCheckBoxHeight.setText("Constrain Height");
        jMenuOptions.add(jCheckBoxHeight);

        jCheckBoxXinvert.setText("X invert");
        jMenuOptions.add(jCheckBoxXinvert);

        jCheckBoxIcons.setText("Basic overlay icons");
        jMenuOptions.add(jCheckBoxIcons);
        jMenuOptions.add(jSeparator3);

        jMenuClear.setText("Clear Trails");
        jMenuClear.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuClearActionPerformed(evt);
            }
        });
        jMenuOptions.add(jMenuClear);

        jMenuMax.setText("Maximise 3D view");
        jMenuMax.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuMaxActionPerformed(evt);
            }
        });
        jMenuOptions.add(jMenuMax);

        jMenuBar1.add(jMenuOptions);

        setJMenuBar(jMenuBar1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jPanel3Dview, javax.swing.GroupLayout.DEFAULT_SIZE, 1284, Short.MAX_VALUE)
                .addGap(18, 18, 18)
                .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jPanel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGap(22, 22, 22))
            .addGroup(layout.createSequentialGroup()
                .addGap(11, 11, 11)
                .addComponent(jPanel3Dview, javax.swing.GroupLayout.DEFAULT_SIZE, 766, Short.MAX_VALUE)
                .addContainerGap())
        );
    }// </editor-fold>//GEN-END:initComponents

    /**
     * This is run when the swing GUI has shown up and is sets off the construction of the 3d world
     * @param evt
     */
    private void formComponentShown(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentShown
        init();
    }//GEN-LAST:event_formComponentShown

    /**
     * Pressing the Database button creates a new sql data source based on the drop down menu item selected
     * @param evt
     */
    private void jButtonDBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonDBActionPerformed
        //Stop any existing datasource threads
        if (ds != null) {
            ds.stop();
        }
        //System.out.println(jComboBoxTable.getSelectedItem().toString());
        //Start a new one
        ds = new DataSource().main(true, dbAddress, jComboBoxTable.getSelectedItem().toString(), jCheckBoxCamTrack.isSelected());
    }//GEN-LAST:event_jButtonDBActionPerformed

    /**
     * Skip forward in the dataset by 10 seconds
     * @param evt
     */
    private void jButtonForwardActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonForwardActionPerformed
        if (ds != null) {
            ds.skip(10000);
        }
    }//GEN-LAST:event_jButtonForwardActionPerformed

    /**
     * Update the shown location for the virtual camera
     * @param evt
     */
    private void jPanel2MouseMoved(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jPanel2MouseMoved
        cameraPosition();
    }//GEN-LAST:event_jPanel2MouseMoved

    /**
     * Skip backwards in the dataset by 10 seconds
     * @param evt
     */
    private void jButtonBackActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonBackActionPerformed
        if (ds != null) {
            ds.skip(-10000);
        }
    }//GEN-LAST:event_jButtonBackActionPerformed

    /**
     * Start a new live datasource recieving data from the Ubisense system
     * @param evt
     */
    private void jButtonLiveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonLiveActionPerformed
        //Stop existing data source
        if (ds != null) {
            ds.stop();
        }
        //Start new live source "12345" is the sink port number to use in Ubisense control software
        ds = new DataSource().main(false, "live", "12345", false);
    }//GEN-LAST:event_jButtonLiveActionPerformed

    private void jPanel3DviewKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jPanel3DviewKeyTyped
        // TODO add your handling code here:
    }//GEN-LAST:event_jPanel3DviewKeyTyped

    /**
     * Switches camera position and perspective based on camera selected
     * @param evt Camera selection buttons on menu bar
     */
    private void CamChange(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_CamChange
        //Get the source hashcode
        int source = evt.getSource().hashCode();

        Transform3D pos2 = new Transform3D();

        //Check which button it matches
        if (source == jMenuCamOffice.hashCode()) {    //Office
            pos2.setTranslation(officeCamVector);
            pos2.setRotation(officeCamQuat);
            jMenuCamOffice.setEnabled(false);
            jMenuCamRoof.setEnabled(true);
            jMenuCamGround.setEnabled(true);
            jMenuCamPlan.setEnabled(true);

        } else if (source == jMenuCamRoof.hashCode()) { //Roof
            pos2.setTranslation(roofCamVector);
            pos2.setRotation(roofCamQuat);
            jMenuCamOffice.setEnabled(true);
            jMenuCamRoof.setEnabled(false);
            jMenuCamGround.setEnabled(true);
            jMenuCamPlan.setEnabled(true);

        } else if (source == jMenuCamGround.hashCode()) { //Ground
            pos2.setTranslation(groundCamVector);
            pos2.setRotation(groundCamQuat);
            jMenuCamOffice.setEnabled(true);
            jMenuCamRoof.setEnabled(true);
            jMenuCamGround.setEnabled(false);
            jMenuCamPlan.setEnabled(true);

        } else {    //Plan view
            pos2.setTranslation(birdCamVector);
            pos2.setRotation(birdCamQuat);
            jMenuCamOffice.setEnabled(true);
            jMenuCamRoof.setEnabled(true);
            jMenuCamGround.setEnabled(true);
            jMenuCamPlan.setEnabled(false);
        }

        //Apply transform
        VpTG.setTransform(pos2);

    }//GEN-LAST:event_CamChange

    /**
     * Menu option to clear all competitor trails
     * @param evt
     */
    private void jMenuClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuClearActionPerformed
        trackRoot.removeAllChildren();
    }//GEN-LAST:event_jMenuClearActionPerformed

    /**
     * Apply filtering parameter
     * @param evt
     */
    private void jCheckBoxFilterActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBoxFilterActionPerformed
        filtered = jCheckBoxFilter.isSelected();
    }//GEN-LAST:event_jCheckBoxFilterActionPerformed

    /**
     * When the maximise option is selected, this creates a new JFrame covering the screen and adds the main view to it.
     * It also sets an event to swap the main view back to the normal panel.
     * @param evt
     */
    private void jMenuMaxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuMaxActionPerformed

        //Setup new window
        Toolkit tk = Toolkit.getDefaultToolkit();
        Dimension d = tk.getScreenSize();
        Frame f = new Frame();
        w = new Window(f);
        w.setSize(d);
        w.add(mainViewCanvas);
        w.setVisible(true);

        //Set up keylistener to return to normal view or start off database playback
        mainViewCanvas.addKeyListener(new java.awt.event.KeyAdapter() {

            @Override
            public void keyTyped(java.awt.event.KeyEvent evt) {
                if (!evt.isShiftDown()) {
                    jPanel3Dview.add(mainViewCanvas);
                    w.dispose();
                } else {
                    if (ds != null) {
                        ds.stop();
                    }
                    jButtonDBActionPerformed(null);
                    System.out.println(jComboBoxTable.getSelectedItem().toString());
                    ds = new DataSource().main(true, dbAddress, jComboBoxTable.getSelectedItem().toString(), jCheckBoxCamTrack.isSelected());
                }
            }
        });
    }//GEN-LAST:event_jMenuMaxActionPerformed

    /**
     * Stop datasource
     * @param evt
     */
    private void jButtonStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonStopActionPerformed
        ds.stop();
    }//GEN-LAST:event_jButtonStopActionPerformed

    /**
     * Start the program and make the main frame visible
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            public void run() {
                new Frame().setVisible(true);

            }
        });
    }

    /**
     * Make the menu bar pop ups heavyweight so they show over the 3d view
     */
    private void heavyMenus() {
        JMenuBar menubar = getJMenuBar();
        int num_menus = menubar.getMenuCount();
        for (int i = 0; i < num_menus; i++) {
            JMenu menu = menubar.getMenu(i);
            JPopupMenu popup = menu.getPopupMenu();
            popup.setLightWeightPopupEnabled(false);
        }
    }

    /**
     * Run at the start to create the 3d world
     */
    public void init() {

        heavyMenus();
        //Get the graphics environment and set up the universe
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        GraphicsConfigTemplate3D confTemp = new GraphicsConfigTemplate3D();
        GraphicsConfiguration config = gd.getBestConfiguration(confTemp);

        mainViewCanvas = new Canvas3D(config);

        u = new SimpleUniverse(mainViewCanvas);

        u.getViewer().getView().setSceneAntialiasingEnable(false);       //Enable Full scene anti-aliasing (requires suitable graphics card)

        u.getViewingPlatform().setNominalViewingTransform();
        VpTG = u.getViewingPlatform().getViewPlatformTransform();
        createSceneGraph();
        u.addBranchGraph(objRoot);


        mainViewCanvas.setSize(jPanel3Dview.getSize()); //Make the 3d area fit within the panel
        jPanel3Dview.add(mainViewCanvas);


        u.addBranchGraph(trackRoot); //Branch for tag breadcrumbs

        //Fix the main camera position once everything is built
        Transform3D camtrf = new Transform3D();
        camtrf.setTranslation(roofCamVector);
        camtrf.setRotation(roofCamQuat);

        VpTG.setTransform(camtrf);
        cameraPosition();
        //End camera 1 stuff


        //Second camera view for plan
        Canvas3D canvas = new Canvas3D(config);
        jPanel3.setLayout(new BorderLayout());
        jPanel3.add(BorderLayout.CENTER, canvas);
        canvas.setSize(jPanel3.getSize());
        Viewer viewer = new Viewer(canvas);
        ViewingPlatform vp = new ViewingPlatform(1);
        viewer.setViewingPlatform(vp);

        Transform3D cam2trf = new Transform3D();
        cam2trf.setTranslation(birdCamVector);
        cam2trf.setRotation(birdCamQuat);
        vp.getViewPlatformTransform().setTransform(cam2trf);

        u.addBranchGraph(vp);
        //End second camera stuff
    }

    /**
     * Update the gui with details of camera position.
     */
    private void cameraPosition() {

        Transform3D tg = new Transform3D();
        VpTG.getTransform(tg);

        Vector3d translation = new Vector3d();
        tg.get(translation);

        posX.setText(String.valueOf(Math.round(translation.x * 100.0) / 100.0));
        posY.setText(String.valueOf(Math.round(translation.y * 100.0) / 100.0));
        posZ.setText(String.valueOf(Math.round(translation.z * 100.0) / 100.0));

        //This doesn't get euler pan tilt angles in degrees...
        Quat4d rotation = new Quat4d();
        tg.get(rotation);
        pan.setText(String.valueOf(Math.round(rotation.y * 100.0) / 100.0));
        tilt.setText(String.valueOf(Math.round(rotation.x * 100.0) / 100.0));

    }

    /**
     * Build the "universe": add all the static objects to the scene
     */
    public void createSceneGraph() {

        //Basic building blocks of 3d objects and movement
        objRoot = new BranchGroup();
        objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
        objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
        objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);

        //Group for breadcrumb tracks
        trackRoot = new BranchGroup();
        trackRoot.setCapability(BranchGroup.ALLOW_DETACH);
        trackRoot.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
        trackRoot.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
        trackRoot.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);


        //Set up colour and shading for track
        Appearance apTrack = new Appearance();
        ColoringAttributes cRed = new ColoringAttributes(new Color3f(java.awt.Color.red), ColoringAttributes.NICEST);
        apTrack.setColoringAttributes(cRed);



        //Set up texture for field
        Appearance apField = new Appearance();
        TextureLoader tl = new TextureLoader("lawn4.png", this);
        ImageComponent2D im = tl.getImage();
        Texture2D tex = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA, im.getWidth(), im.getHeight());
        tex.setImage(0, im);
        apField.setTexture(tex);
        apField.setTransparencyAttributes(new TransparencyAttributes(TransparencyAttributes.FASTEST, 0.1f));

        //Create the ground plane with the above loaded texture
        Box field = new Box(5.12f, 0.03f, 5.12f, Primitive.GENERATE_NORMALS + Primitive.GENERATE_TEXTURE_COORDS, apField);

        //Create a transform to move the field to the correct location
        Transform3D moveField = new Transform3D();
        moveField.setTranslation(new Vector3f(0f, 0f, -0.1f));

        TransformGroup fieldTransform = new TransformGroup();
        fieldTransform.setTransform(moveField);
        fieldTransform.addChild(field);
        if (drawField) {
            objRoot.addChild(fieldTransform);
        }
        Transform3D t3d = new Transform3D();

        //Set up camera "icon"
        t3d.setTranslation(roofCamVector);
        TransformGroup t = new TransformGroup();
        t.addChild(new Box(0.02f, 0.02f, 0.02f, apTrack));
        t.setTransform(t3d);
        objRoot.addChild(t);

        //Set up camera "icon" 2
        Transform3D t3d2 = new Transform3D();
        t3d2.setTranslation(groundCamVector);
        TransformGroup t2 = new TransformGroup();
        t2.addChild(new Box(0.02f, 0.02f, 0.02f, apTrack));
        t2.setTransform(t3d2);
        objRoot.addChild(t2);

        //Set up camera "icon" 3 :26.001, N:2.632, H:6.636
        Transform3D t3d3 = new Transform3D();
        t3d3.setTranslation(officeCamVector);
        TransformGroup t3 = new TransformGroup();
        t3.addChild(new Box(0.02f, 0.02f, 0.02f, apTrack));
        t3.setTransform(t3d3);
        objRoot.addChild(t3);

        //Set up Sensor positions
        Vector3d[] sensorPoints = {new Vector3d(-2.636, 3.626, -13.799),
            new Vector3d(-4.306, 3.011, 13.709), new Vector3d(32.806, 3.884, 13.070), new Vector3d(32.730, 4.011, -13.854)};

        //Sensor appearance
        Appearance apSensor = new Appearance();
        ColoringAttributes cGray = new ColoringAttributes(new Color3f(java.awt.Color.gray), ColoringAttributes.NICEST);
        apSensor.setColoringAttributes(cGray);

        //Setup sensor objects
        BranchGroup sensors = new BranchGroup();
        for (int i = 0; i < sensorPoints.length; i++) {
            TransformGroup tempTrg = new TransformGroup();
            tempTrg.addChild(new Box(0.005f, 0.02f, 0.005f, apSensor));
            Transform3D tempT3d = new Transform3D();
            tempT3d.setTranslation(sensorPoints[i]);
            tempTrg.setTransform(tempT3d);
            sensors.addChild(tempTrg);
        }
        objRoot.addChild(sensors);


        //Set up calibration marker points from demo recording
        Appearance apMark = new Appearance();
        ColoringAttributes cWhite = new ColoringAttributes(new Color3f(java.awt.Color.white), ColoringAttributes.NICEST);
        apMark.setColoringAttributes(cWhite);

        // double[][] markersCoords = {{0f, 0f}, {-0.002, -2.2295}, {-0.7785, -1.5342}, {-0.0037, -1.5104}, {0.7495, -0.0346}, {-0.7527, 0.0304}};

        //Marker points on croquet lawn 1st set
        // Calibration One (-0.020, -22.295, -0.019);
        // Calibration Two (7.785, -15.342, 0.006);
        // Calibration Three (-0.037, -15.104, 0.033);
        // Calibration Four (7.495, -0.346, 0.086);
        // Calibration Five (-7.527, 0.304, 0.057);

        double[][] markersCoords = {{0f, 0f}, {0.4503, -0.9753}, {0.4765, -0.5062}, {0.4832, 0.0044}, {0.5042, 0.5041}, //0,5m N
            {0.9710, -0.9932}, {0.9989, -0.5034}, {0.9913, 0.0011}, {1.0276, 0.5301}, {1.0139, 1.0072}, //10m N
            {1.5149, -0.9994}, {1.5444, -0.5125}, {1.5483, -0.0267}, {1.5653, 0.5215}, {1.5751, 1.0038}, //15m N
            {2.0396, -1.0191}, {2.0475, -0.5112}, {2.0153, -0.0085}, {2.0122, 0.5111}, {2.0532, 0.9850}, //20m N
            {2.5284, -0.0058}}; //25m N

        Box[] markers = new Box[markersCoords.length];
        TransformGroup[] markerPositions = new TransformGroup[markers.length];

        //Set up the marker blocks
        for (int i = 0; i < markers.length; i++) {
            markers[i] = new Box(0.005f, 0.02f, 0.005f, apMark);
            markerPositions[i] = new TransformGroup();

            t3d = new Transform3D();
            //float xPt = (i * 20) - 100;
            t3d.setTranslation(new Vector3f((float) markersCoords[i][0], 0.025f, (float) markersCoords[i][1]));
            markerPositions[i].setTransform(t3d);
            markerPositions[i].addChild(markers[i]);

            objRoot.addChild(markerPositions[i]);
        }

        enableMouseMovement(); //Enable basic mouse navigation


        //Setup lighting
        BranchGroup lightGroup = new BranchGroup();
        // Set up the ambient light
        Color3f ambientColor = new Color3f(0.5f, 0.5f, 0.5f);
        AmbientLight ambientLightNode = new AmbientLight(ambientColor);
        ambientLightNode.setInfluencingBounds(new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 500.0));
        lightGroup.addChild(ambientLightNode);

        //Setup point light
        Color3f light1Color = new Color3f(2f, 2f, 2f);
        BoundingSphere b1 = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 1000.0);
        Vector3f light1Direction = new Vector3f(3f, -10f, -4f);
        DirectionalLight light1 = new DirectionalLight(light1Color, light1Direction);
        light1.setInfluencingBounds(b1);
        lightGroup.addChild(light1);

        u.addBranchGraph(lightGroup);
        objRoot.compile();


    }

    /**
     * This makes the 3d window responsive to mouse events
     */
    public void enableMouseMovement() {
        TransformGroup vpTrans = u.getViewingPlatform().getViewPlatformTransform();
        BoundingSphere mouseBounds = null;
        mouseBounds = new BoundingSphere(new Point3d(), 10000.0);


        MouseRotate myMouseRotate = new MouseRotate(MouseBehavior.INVERT_INPUT);
        myMouseRotate.setTransformGroup(vpTrans);
        myMouseRotate.setSchedulingBounds(mouseBounds);
        objRoot.addChild(myMouseRotate);

        MouseTranslate myMouseTranslate = new MouseTranslate(MouseBehavior.INVERT_INPUT);
        myMouseTranslate.setTransformGroup(vpTrans);
        myMouseTranslate.setSchedulingBounds(mouseBounds);
        objRoot.addChild(myMouseTranslate);

        MouseZoom myMouseZoom = new MouseZoom(MouseBehavior.INVERT_INPUT);
        myMouseZoom.setTransformGroup(vpTrans);
        myMouseZoom.setSchedulingBounds(mouseBounds);
        objRoot.addChild(myMouseZoom);


    }

    /** Takes a call from one of the data threads with a position update
     * @param tag   Tag Id
     * @param x Float x position
     * @param y Float y position
     * @param z Float z position
     * @param tc Timecode
     */
    public void updatePosition(int tag, float x, float y, float z, int tc) {

        System.out.println("Tag: "+tag+" coords: "+x+" "+y+" "+z+"c");
        int[] time = revTimecode(tc - 4000); //-4000 to correct for ubisense delay

        //Add timecode
        jLabelH.setText(String.valueOf(time[0]));
        jLabelM.setText(String.valueOf(time[1]));
        jLabelS.setText(String.valueOf(time[2]));
        jLabelMs.setText(String.valueOf(time[3]));

        Transform3D update = new Transform3D();                 //Set up a new transform for updating position
        Transform3D hide = new Transform3D();
        hide.setTranslation(new Vector3f(0, +1000, 0));              //Set up a transform to hide non-active tags

        //Menu options for positioning
        //Fix tag icon height
        if (jCheckBoxHeight.isSelected()) {
            z = 0.9f;
        }
        //Invert x position to correct for data capture error in one dataset
        if (jCheckBoxXinvert.isSelected()) {
            x = 0 - x;
        }
        //If using overlay style icons, fix to the ground at a suitable height
        if (jCheckBoxIcons.isSelected()) {
            z = 0.001f;
        }

        update.setTranslation(new Vector3f(y / 10, z / 10, x / 10));           //Set the new position

        //If the tag hasn't been displayed already, add an icon for it
        if (!competitors.containsKey(tag)) {
            addCompetitor(tag);
        }
        //Apply the position
        competitors.get(tag).setTransform(update);
        //Store the update time
        compLastUpdates.put(tag, tc);
        //Add a breadcrumb
        addTrack(tag, update);

        //Check for any competitors who haven't moved in a while
        //Assuming the tag has lost signal/battery/been removed from field
        //remove the icon from the field
        Enumeration<Integer> e = compLastUpdates.keys();
        while (e.hasMoreElements()) {
            int i = e.nextElement();
            if (compLastUpdates.get(i) < tc - 10000) {
                competitors.get(i).setTransform(hide);
            }
        }
        //Remove any extra breadcrumbs over limit
        if (trackRoot.numChildren() > maxTracks) {
            trackRoot.removeChild(0);

        }
       
    }

    /**
     * Add a competitor to the scene
     *
     * @param tag
     */
    private void addCompetitor(int tag) {
        //Set up competitor coloring
        ColoringAttributes cA = new ColoringAttributes(new Color3f(colorBox[colorToUse]), ColoringAttributes.NICEST);      //Color from palette
        colorToUse++;
        if (colorToUse > colorBox.length) {
            colorToUse = 0;
        }
        Appearance ap = new Appearance();
        ap.setRenderingAttributes(new RenderingAttributes());
        ap.getRenderingAttributes().setCapability(RenderingAttributes.ALLOW_VISIBLE_READ);
        ap.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ);
        ap.setColoringAttributes(cA);
        Material mat = new Material(new Color3f(colorBox[colorToUse]), new Color3f(Color.black), new Color3f(colorBox[colorToUse]), new Color3f(Color.white), 70f);
        ap.setMaterial(mat);
        TransformGroup personTrg = new TransformGroup();

        //Add text appropriate for the tag id - this should be taken out to a database for the names
        if (competitorText) {
            String tagText;
            switch (tag) {
                case 335550570:
                    tagText = "Dave M";
                    break;
                case 335550552:
                    tagText = "Rob D";
                    break;
                case 335550577:
                    tagText = "Sam D";
                    break;
                case 335550249:
                    tagText = "Chris P";
                    break;
                case 335550318:
                    tagText = "Tuck P";
                    break;
                case 335550563:
                    tagText = "Yang X";
                    break;
                default:
                    tagText = String.valueOf(tag);
                    break;
            }
            Transform3D textTrf = new Transform3D();
            textTrf.setScale(0.02);
            Font3D font = new Font3D(new Font("Helvetica", Font.PLAIN, 1), new FontExtrusion());
            Text3D text = new Text3D(font, tagText, new Point3f(0f, -1f, 2f));
            text.setAlignment(Text3D.ALIGN_CENTER);
            Shape3D textShape = new Shape3D(text);
            TransformGroup textTrg = new TransformGroup();
            textTrg.setTransform(textTrf);
            textTrg.addChild(textShape);
            personTrg.addChild(textTrg);
        }
        //If using overlay icons, set up the alternate icon
        if (jCheckBoxIcons.isSelected()) {
            Appearance ap2 = new Appearance();
            ColoringAttributes c2 = new ColoringAttributes(new Color3f(Color.black), ColoringAttributes.NICEST);
            ap2.setColoringAttributes(c2);
            Cylinder cy = new Cylinder(0.05f, 0.001f, ap);
            Cylinder cy2 = new Cylinder(0.04f, 0.002f, ap2);
            personTrg.addChild(cy);
            personTrg.addChild(cy2);
        } else {    //Otherwise use regular icons
            Box cb = new Box(0.04f, 0.15f, 0.04f, ap);            //Set body size + color
            Sphere ch = new Sphere(0.040f, ap);                   //Set head size + color

            //Move sphere to suitable location
            TransformGroup sphereTrg = new TransformGroup();
            sphereTrg.addChild(ch);
            Transform3D sphereTrf = new Transform3D();
            sphereTrf.setTranslation(new Vector3f(0f, 0.17f, 0f));
            sphereTrg.setTransform(sphereTrf);

            //Add 3d objects to the competitor array and 'hide' under the playing field.
            personTrg.addChild(cb);
            personTrg.addChild(sphereTrg);
        }
        Transform3D tr = new Transform3D();
        tr.setTranslation(new Vector3f(0f, -0.2f, 0f));
        personTrg.setTransform(tr);
        personTrg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        //Add to competitors list and 3d world
        competitors.put(tag, personTrg);
        BranchGroup node = new BranchGroup();
        node.addChild(competitors.get(tag));
        objRoot.addChild(node);
    }

    /**
     * Adds a small box to the track in the position of the latest update.
     * Cleared by removing all children from trackRoot
     * @param tag
     * @param position
     */
    private void addTrack(int tag, Transform3D position) {

        Appearance ap = ((Primitive) competitors.get(tag).getChild(1)).getAppearance();
        Box cb = new Box(0.002f, 0.002f, 0.002f, ap);
        TransformGroup trackTrg = new TransformGroup();
        trackTrg.addChild(cb);
        trackTrg.setTransform(position);

        BranchGroup node = new BranchGroup();
        node.addChild(trackTrg);
        node.setCapability(BranchGroup.ALLOW_DETACH);
        trackRoot.addChild(node);
    }

    /**
     * Utility function to convert millisecond timecode to normal time format
     * @param tc
     * @return
     */
    private int[] revTimecode(int tc) {
        return new int[]{(int) Math.floor(tc / 3600000), (int) Math.floor((tc % 3600000) / 60000),
                    (int) Math.floor((tc % 60000) / 1000), (int) Math.floor(tc % 1000)};

    }

    /**
     * Move the camera position to match tracked data
     * @param ptrf [Pan, tilt, roll, fov]
     */
    public void updateCamera(float[] ptrf) {

        //Get existing transform
        Transform3D moveCam = new Transform3D();
        VpTG.getTransform(moveCam);

        //Create a matrix for each component of the direction
        Matrix3f t = new Matrix3f();
        t.rotX((float) Math.toRadians(ptrf[1]));
        Matrix3f p = new Matrix3f();
        p.rotY((float) Math.toRadians(ptrf[0]));
        Matrix3f r = new Matrix3f();
        r.rotZ((float) Math.toRadians(ptrf[2]));

        //Multipy together to produce single matrix with full direction component
        t.mul(p);
        t.mul(r);

        //Record current cam position as it will be overwritte
        Vector3d v = new Vector3d();
        moveCam.get(v);

        //Set camera direction, position, scale?
        moveCam.set(t, v, 1d);

        //Apply transform
        VpTG.setTransform(moveCam);
        cameraPosition();
        u.getViewer().getView().setFieldOfView(Math.toRadians(ptrf[3]) * 1.77777);
    }

    /**
     * Update the frame number in the gui when using camera tracking DB
     * @param f Frame number
     */
    public void updateFrame(int f) {
        jLabelFrame.setText(String.valueOf(f));
    }


    Bounds camBounds;   //Limits for user controlled camera motion
    Window w;    //Window for maximised view
    SimpleUniverse u;   //Base of the universe
    TransformGroup VpTG;    //Viewpoint (camera) transform group
    Canvas3D mainViewCanvas;    //Main 3d canvas
    DataSource ds;              //Active tag data source
    BranchGroup objRoot;        //Universe object root, everything gets added to this
    BranchGroup trackRoot;      //Root for breadcrumb tracks

    //Color palette for competitors
    Color[] colorBox = {Color.magenta, Color.cyan, Color.yellow, Color.pink, Color.orange, Color.GREEN, Color.RED, Color.BLACK, Color.darkGray, Color.gray, Color.white, Color.LIGHT_GRAY, Color.blue};
    int colorToUse = 0; //Current color to use for competitor
    Hashtable<Integer, Integer> compLastUpdates = new Hashtable<Integer, Integer>(); //Time of last update for competitors
    Hashtable<Integer, TransformGroup> competitors = new Hashtable<Integer, TransformGroup>();  //Competitor objects

    //Camera parameters
    Vector3f roofCamVector = new Vector3f(1.3494f, 0.73f, 2.7576f); //corrected for seq a
    // Vector3f roofCamVector = new Vector3f(1.3494f, 0.75f, 2.7576f); //Correction for seq2
    //Vector3f roofCamVector = new Vector3f(1.3494f, 0.6992f, 2.7576f);
    Quat4d roofCamQuat = new Quat4d(-0.1405582830721674, -0.04488278148025761, 0.013097496769880913, 0.9889678258491338);
    Vector3f officeCamVector = new Vector3f(0.2632f, 0.6636f, 2.6001f);
    Quat4d officeCamQuat = new Quat4d(-0.1101079664207571, -0.22542152451658032, -0.04367942038153547, 0.9670333397822636);
    Vector3f groundCamVector = new Vector3f(3.3886f, 0.1998f, 0.5117f);
    Quat4d groundCamQuat = new Quat4d(-0.03157395836052181, 0.5498608191727821, 0.03781008577309382, 0.8338024718756336);
    Vector3f birdCamVector = new Vector3f(1.5f, 5f, 0f);
    Quat4d birdCamQuat = new Quat4d(-0.5, 0.5, 0.5, 0.5);
    
    /**
     * Hard-coded variable which haven't made it into the GUI:
     */
    int maxTracks = 50; //Limit for the number of breadcrumbs
    String dbAddress = "jdbc:mysql://localhost:3306/events";    //MySQL Database URL
    public boolean filtered = false; //Apply filtering to data (10 point window average)
    boolean drawField = true;   //Draw textures, false for keying
    boolean competitorText = true;  //Display text on competitor icons

    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jButtonBack;
    private javax.swing.JButton jButtonDB;
    private javax.swing.JButton jButtonForward;
    private javax.swing.JButton jButtonLive;
    private javax.swing.JButton jButtonStop;
    private javax.swing.JButton jButtonWS;
    private javax.swing.JCheckBoxMenuItem jCheckBoxCamTrack;
    private javax.swing.JCheckBoxMenuItem jCheckBoxFilter;
    private javax.swing.JCheckBoxMenuItem jCheckBoxHeight;
    private javax.swing.JCheckBoxMenuItem jCheckBoxIcons;
    private javax.swing.JCheckBoxMenuItem jCheckBoxXinvert;
    private javax.swing.JComboBox jComboBoxTable;
    private javax.swing.JFormattedTextField jFormattedTextField1;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabel5;
    private javax.swing.JLabel jLabel6;
    private javax.swing.JLabel jLabelFrame;
    private javax.swing.JLabel jLabelH;
    private javax.swing.JLabel jLabelM;
    private javax.swing.JLabel jLabelMs;
    private javax.swing.JLabel jLabelS;
    private javax.swing.JLabel jLabelvisible;
    private javax.swing.JMenu jMenu1;
    private javax.swing.JMenuBar jMenuBar1;
    private javax.swing.JMenu jMenuCam;
    private javax.swing.JMenuItem jMenuCamGround;
    private javax.swing.JMenuItem jMenuCamOffice;
    private javax.swing.JMenuItem jMenuCamPlan;
    private javax.swing.JMenuItem jMenuCamRoof;
    private javax.swing.JMenuItem jMenuClear;
    private javax.swing.JMenuItem jMenuMax;
    private javax.swing.JMenu jMenuOptions;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JPanel jPanel3;
    private javax.swing.JPanel jPanel3Dview;
    private javax.swing.JPanel jPanel4;
    private javax.swing.JProgressBar jProgressBar1;
    private javax.swing.JSeparator jSeparator1;
    private javax.swing.JSeparator jSeparator2;
    private javax.swing.JSeparator jSeparator3;
    private javax.swing.JTextField jTextField1;
    private javax.swing.JTextField jTextField2;
    private javax.swing.JTextField pan;
    private javax.swing.JTextField posX;
    private javax.swing.JTextField posY;
    private javax.swing.JTextField posZ;
    private javax.swing.JTextField tilt;
    // End of variables declaration//GEN-END:variables
}
